/*
* Copyright (c) 2020 The canvas Authors. All rights reserved.
  Use of this source code is governed by a BSD-style license that can be
  found in the LICENSE file.

* Author:         dzlua - https://gitee.com/dzlua/canvas.git
* Email:          505544956@qq.com
* FileName:       render.cc
* Create Time:    2020/01/05 10:38:43
* Description:    /
*/

#include "render.h"

namespace {
  std::wstring ca2w(const char *str) {
    std::wstring wstr;
	  int nLenOfWideCharStr = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
    wstr.resize(nLenOfWideCharStr);
	  MultiByteToWideChar(CP_ACP, 0, str, -1, (LPWSTR)wstr.data(), nLenOfWideCharStr);
    return std::move(wstr);
  }
}

namespace cvs {
namespace c2d {
  // CustomTextRender Impl
  CustomTextRender::CustomTextRender(ID2D1Factory *d2d_factory)
      : ref_count_(1), d2d_factory_(d2d_factory) {
    if (d2d_factory_) d2d_factory_->AddRef();
    matrix_ = D2D1::IdentityMatrix();
    dpi_ = 96.f;
  }
  CustomTextRender::~CustomTextRender() { if (d2d_factory_) d2d_factory_->Release(); reset(); }
  void CustomTextRender::reset() {
    for (auto g : texts_)
      g->Release();
    texts_.clear();
    for (auto g : underlines_)
      g->Release();
    underlines_.clear();
    for (auto g : strikethroughs_)
      g->Release();
    strikethroughs_.clear();
  }
  HRESULT CustomTextRender::transGeometry(ID2D1Geometry *geometry
          , FLOAT baselineOriginX, FLOAT baselineOriginY
          , ID2D1TransformedGeometry **transformed_geometry) {
    if (!d2d_factory_) return E_FAIL;
    if (!geometry || !transformed_geometry) return E_INVALIDARG;
    auto matrix = D2D1::Matrix3x2F(
      1.0f, 0.0f,
      0.0f, 1.0f,
      baselineOriginX, baselineOriginY
    );
    return d2d_factory_->CreateTransformedGeometry(geometry, matrix, transformed_geometry);
  }
  void CustomTextRender::SetEnv(FLOAT dpi, const D2D1_MATRIX_3X2_F &matrix) {
    matrix_ = matrix; dpi_ = dpi;
  }
  HRESULT CustomTextRender::BeginDraw() {
    reset();
    if (!d2d_factory_) return E_FAIL;
    return S_OK;
  }
  HRESULT CustomTextRender::EndDraw() { reset(); return S_OK; }
  HRESULT CustomTextRender::Geometry(std::vector<ID2D1TransformedGeometry*> *texts
          , std::vector<ID2D1TransformedGeometry*> *underlines
          , std::vector<ID2D1TransformedGeometry*> *strikethroughs) const {
    if (texts) {
      for (auto g : texts_) {
        g->AddRef();
        texts->push_back(g);
      }
    }
    if (underlines) {
      for (auto g : underlines_) {
        g->AddRef();
        underlines->push_back(g);
      }
    }
    if (strikethroughs) {
      for (auto g : strikethroughs_) {
        g->AddRef();
        strikethroughs->push_back(g);
      }
    }
    return S_OK;
  }
  //
  HRESULT CustomTextRender::DrawGlyphRun(
      __maybenull void* clientDrawingContext,
      FLOAT baselineOriginX,
      FLOAT baselineOriginY,
      DWRITE_MEASURING_MODE measuringMode,
      __in DWRITE_GLYPH_RUN const* glyphRun,
      __in DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription,
      __maybenull IUnknown* clientDrawingEffect) {
    if (!d2d_factory_) return E_FAIL;

    ID2D1PathGeometry *geometry = 0;
    auto hr = d2d_factory_->CreatePathGeometry(&geometry);
    if (FAILED(hr)) return hr;

    ID2D1GeometrySink *sink = 0;
    hr = geometry->Open(&sink);

    if (SUCCEEDED(hr)) {
      hr = glyphRun->fontFace->GetGlyphRunOutline(
          glyphRun->fontEmSize
        , glyphRun->glyphIndices
        , glyphRun->glyphAdvances
        , glyphRun->glyphOffsets
        , glyphRun->glyphCount
        , glyphRun->isSideways
        , glyphRun->bidiLevel % 2
        , sink);
    }

    if (SUCCEEDED(hr))
      hr = sink->Close();

    if (SUCCEEDED(hr)) {
      ID2D1TransformedGeometry *transformed_geometry = 0;
      hr = transGeometry(geometry
          , baselineOriginX, baselineOriginY
          , &transformed_geometry);
      if (SUCCEEDED(hr))
        texts_.push_back(transformed_geometry);
    }

    if (sink) sink->Release();
    if (geometry) geometry->Release();

    return hr;
  }
  HRESULT CustomTextRender::DrawUnderline(
      __maybenull void* clientDrawingContext,
      FLOAT baselineOriginX,
      FLOAT baselineOriginY,
      __in DWRITE_UNDERLINE const* underline,
      __maybenull IUnknown* clientDrawingEffect) {
    if (!d2d_factory_) return E_FAIL;

    D2D1_RECT_F rect;
    rect.left = 0;
    rect.top = underline->offset;
    rect.right = underline->width;
    rect.bottom = rect.top + underline->thickness;

    ID2D1RectangleGeometry *geometry = 0;
    auto hr = d2d_factory_->CreateRectangleGeometry(rect, &geometry);

    if (SUCCEEDED(hr)) {
      ID2D1TransformedGeometry *transformed_geometry = 0;
      hr = transGeometry(geometry
          , baselineOriginX, baselineOriginY
          , &transformed_geometry);
      if (SUCCEEDED(hr))
        underlines_.push_back(transformed_geometry);
    }

    if (geometry) geometry->Release();

    return hr;
  }
  HRESULT CustomTextRender::DrawStrikethrough(
      __maybenull void* clientDrawingContext,
      FLOAT baselineOriginX,
      FLOAT baselineOriginY,
      __in DWRITE_STRIKETHROUGH const* strikethrough,
      __maybenull IUnknown* clientDrawingEffect) {
    if (!d2d_factory_) return E_FAIL;

    D2D1_RECT_F rect;
    rect.left = 0;
    rect.top = strikethrough->offset;
    rect.right = strikethrough->width;
    rect.bottom = rect.top + strikethrough->thickness;

    ID2D1RectangleGeometry *geometry = 0;
    auto hr = d2d_factory_->CreateRectangleGeometry(rect, &geometry);

    if (SUCCEEDED(hr)) {
      ID2D1TransformedGeometry *transformed_geometry = 0;
      hr = transGeometry(geometry
          , baselineOriginX, baselineOriginY
          , &transformed_geometry);
      if (SUCCEEDED(hr))
        strikethroughs_.push_back(transformed_geometry);
    }

    if (geometry) geometry->Release();

    return hr;
  }
  HRESULT CustomTextRender::DrawInlineObject(
      __maybenull void* clientDrawingContext,
      FLOAT originX,
      FLOAT originY,
      IDWriteInlineObject* inlineObject,
      BOOL isSideways,
      BOOL isRightToLeft,
      __maybenull IUnknown* clientDrawingEffect) {
    return inlineObject->Draw(clientDrawingContext, this, originX, originY, isSideways, isRightToLeft, clientDrawingEffect);
  }

  HRESULT CustomTextRender::IsPixelSnappingDisabled(
      __maybenull void* clientDrawingContext,
      __out BOOL* isDisabled) {
    *isDisabled = FALSE;
    return S_OK;
  }
  HRESULT CustomTextRender::GetCurrentTransform(
      __maybenull void* clientDrawingContext,
      __out DWRITE_MATRIX* transform) {
    transform->m11 = matrix_._11;
    transform->m12 = matrix_._12;
    transform->m21 = matrix_._21;
    transform->m22 = matrix_._22;
    transform->dx = matrix_._31;
    transform->dy = matrix_._32;
    return S_OK;
  }
  HRESULT CustomTextRender::GetPixelsPerDip(
      __maybenull void* clientDrawingContext,
      __out FLOAT* pixelsPerDip) {
    *pixelsPerDip = dpi_ / 96.f;
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE CustomTextRender::QueryInterface( 
      /* [in] */ REFIID riid,
      /* [iid_is][out] */ __RPC__deref_out void __RPC_FAR *__RPC_FAR *ppvObject) {
    if (riid == IID_IUnknown) {
      *ppvObject = this;
      AddRef();
      return S_OK;
    }
    return E_NOINTERFACE;
  }
  ULONG STDMETHODCALLTYPE CustomTextRender::AddRef( void) { return ++ref_count_; }
  ULONG STDMETHODCALLTYPE CustomTextRender::Release( void) {
    if (0 == --ref_count_) delete this;
    return ref_count_;
  }
} // end namespace c2d
} // end namespace cvs

namespace cvs {
  bool supported() {
    return c2d::D2DRender::is_supported();
  }
  C2DorC3D c2d_or_c3d() { return C2DorC3D::C2D; }
  c2d::RenderType c2d::render_type() { return c2d::RenderType::D2D; }
  Result c2d::create_render(IFactory *factory, c2d::IRender **render) {
    if (!render) return CVS_E_InvalidParam;
    c2d::D2DRender *r = new c2d::D2DRender(factory);
    if (!r) return CVS_E_OutOfMemory;
    Result rst = r->init();
    if (Failed(rst)) {
      SaveDecref(r);
      r = 0;
    }
    *render = r;
    return rst;
  }
namespace c2d {
  // D2DResource impl
  D2DResource::D2DResource(D2DRender *render) : render_(render) {}
  D2DResource::~D2DResource() {}
  IRender* D2DResource::render() const { return render_; }
  D2DRender* D2DResource::d2d_render() const { return render_; }
  IFactory* D2DResource::factory() const { return render_->factory(); }
  ID2D1Factory* D2DResource::d2d_factory() const { return render_->d2d_factory(); }
  IDWriteFactory* D2DResource::dwrite_factory() const { return render_->dwrite_factory(); }
  CustomTextRender* D2DResource::text_render() const { return render_->text_render(); }
  
  // D2DTargetResource impl
  D2DTargetResource::D2DTargetResource(D2DRender *render, D2DTarget *target) : D2DResource(render), target_(target) {}
  D2DTargetResource::~D2DTargetResource() {}
  ITarget* D2DTargetResource::target() const { return target_; }
  ID2D1RenderTarget* D2DTargetResource::d2d_target() const {
    if (target_) return target_->d2d_target();
    return 0;
  }

  ///////////////////////////////////////////
  // D2DSurfaceData impl
  D2DSurfaceData::D2DSurfaceData(D2DRender *render) : D2DResource(render), wic_lock_(0) {}
  D2DSurfaceData::~D2DSurfaceData() { reset(); }
  Result D2DSurfaceData::reset() {
    if (wic_lock_) {
      wic_lock_->Release();
      wic_lock_ = 0;
    }
    return CVS_S_Ok;
  }
  Size D2DSurfaceData::size() const {
    if (wic_lock_) {
      UINT w, h;
      auto hr = wic_lock_->GetSize(&w, &h);
      if (SUCCEEDED(hr)) return Size(w, h);
    }
    return Size();
  }
  uint D2DSurfaceData::stride() const {
    if (wic_lock_) {
      UINT w;
      auto hr = wic_lock_->GetStride(&w);
      if (SUCCEEDED(hr)) return w;
    }
    return 0;
  }
  Result D2DSurfaceData::pixels(uint *buffer_size, void **data) const {
    if (!wic_lock_) return CVS_E_Fail;
    UINT s;
    WICInProcPointer p;
    auto hr = wic_lock_->GetDataPointer(&s, &p);
    if (SUCCEEDED(hr)) {
      *buffer_size = s;
      *data = p;
    }
    return SUCCEEDED(hr) ? CVS_S_Ok : CVS_E_Fail;
  }

  Result D2DSurfaceData::create(IWICBitmap *bitmap) {
    if (!bitmap) return CVS_E_InvalidParam;
    
    UINT w, h;
    auto hr = bitmap->GetSize(&w, &h);
    if (FAILED(hr)) return CVS_E_Fail;

    reset();
    
    WICRect rc = { 0, 0, (INT)w, (INT)h };
    hr = bitmap->Lock(&rc, WICBitmapLockWrite, &wic_lock_);

    return SUCCEEDED(hr) ? CVS_S_Ok : CVS_E_Fail;
  }

  // D2DSurface impl
  D2DSurface::D2DSurface(D2DRender *render) : D2DResource(render), wic_bitmap_(0) {}
  D2DSurface::~D2DSurface() { reset(); }
  Result D2DSurface::reset() {
    if (wic_bitmap_) {
      wic_bitmap_->Release();
      wic_bitmap_ = 0;
    }
    return CVS_S_Ok;
  }
  Result D2DSurface::create(const PixelFormat format, const Size &size) {
    if (size.w <= 0.f || size.h <= 0.f) return CVS_E_InvalidParam;
    reset();

    IWICImagingFactory *factory = create_wic_factory();
    if (!factory) return CVS_E_Fail;

    // create bitmap
    auto hr = factory->CreateBitmap(size.w, size.h
        , wic_pixel_format(format)
        , WICBitmapCreateCacheOption::WICBitmapCacheOnDemand
        , &wic_bitmap_);
    if (FAILED(hr) || !wic_bitmap_) {
      factory->Release();
      return CVS_E_RenderSurfaceError;
    }

    // clear bitmap data to 0.
    WICRect rc = { 0, 0, (INT)size.w, (INT)size.h };
    IWICBitmapLock *lock = 0;
    hr = wic_bitmap_->Lock(&rc, WICBitmapLockWrite, &lock);
    if (SUCCEEDED(hr)) {
      UINT buffer_size = 0;
      WICInProcPointer pbuf = 0;

      hr = lock->GetDataPointer(&buffer_size, &pbuf);
      if (SUCCEEDED(hr)) {
        ZeroMemory(pbuf, buffer_size);
      }
      
      lock->Release();
    }

    factory->Release();

    return CVS_S_Ok;
  }
  Result D2DSurface::load_file(const char *file) {
    if (!file || !*file) return CVS_E_InvalidParam;
    std::wstring file_path = ca2w(file);
    if (file_path.empty()) return CVS_E_InvalidParam;

    reset();

    IWICImagingFactory *factory = create_wic_factory();
    IWICBitmapDecoder *decoder = 0;
    IWICBitmapFrameDecode *source = 0;

    if (!factory) return CVS_E_Fail;

    auto hr = factory->CreateDecoderFromFilename(file_path.c_str()
        , 0, GENERIC_READ, WICDecodeMetadataCacheOnLoad
        , &decoder);

    if (SUCCEEDED(hr))
      hr = decoder->GetFrame(0, &source);

    Result rst = create_wic_bitmap(factory, source);

    if (source) source->Release();
    if (decoder) decoder->Release();

    factory->Release();

    return rst;
  }
  Result D2DSurface::load_buffer(const char *buffer, ulong size) {
    if (!buffer || !*buffer || size == 0) return CVS_E_InvalidParam;
    std::wstring file_path = ca2w(buffer);
    if (file_path.empty()) return CVS_E_InvalidParam;

    reset();

    IWICImagingFactory *factory = create_wic_factory();
    IWICStream *stream = 0;
    IWICBitmapDecoder *decoder = 0;
    IWICBitmapFrameDecode *source = 0;

    if (!factory) return CVS_E_Fail;

    auto hr = factory->CreateStream(&stream);

    if (SUCCEEDED(hr))
      hr = stream->InitializeFromMemory((WICInProcPointer)buffer, size);
    
    if (SUCCEEDED(hr)) {
      hr = factory->CreateDecoderFromStream(stream
        , 0, WICDecodeMetadataCacheOnLoad
        , &decoder);
    }

    if (SUCCEEDED(hr))
      hr = decoder->GetFrame(0, &source);

    Result rst = create_wic_bitmap(factory, source);

    if (source) source->Release();
    if (decoder) decoder->Release();
    if (stream) stream->Release();

    factory->Release();

    return rst;
  }
  Result D2DSurface::save(const char *file, SurfaceType type) const {
    if (!file || !*file) return CVS_E_InvalidParam;
    if (!wic_bitmap_) return CVS_E_RenderSurfaceError;

    std::wstring file_path = ca2w(file);
    if (file_path.empty()) return CVS_E_InvalidParam;

    WICPixelFormatGUID format_guid = GUID_WICPixelFormat32bppPBGRA;
    HRESULT hr = wic_bitmap_->GetPixelFormat(&format_guid);
    if (FAILED(hr)) return CVS_E_Fail;

    UINT width, height;
    hr = wic_bitmap_->GetSize(&width, &height);
    if (FAILED(hr)) return CVS_E_Fail;

    GUID guid = wic_container_format(type);

    IWICImagingFactory *factory = create_wic_factory();
    IWICStream *stream = 0;
    IWICBitmapEncoder *encoder = 0;
    IWICBitmapFrameEncode *frame = 0;

    if (!factory) return CVS_E_Fail;

    hr = factory->CreateStream(&stream);
    if (SUCCEEDED(hr))
      hr = stream->InitializeFromFilename(file_path.c_str(), GENERIC_WRITE);
    
    if (SUCCEEDED(hr))
      hr = factory->CreateEncoder(guid, NULL, &encoder);
      
    if (SUCCEEDED(hr))
      hr = encoder->Initialize(stream, WICBitmapEncoderCacheOption::WICBitmapEncoderNoCache);

    if (SUCCEEDED(hr))
      hr = encoder->CreateNewFrame(&frame, NULL);
    
    if (SUCCEEDED(hr))
      hr = frame->Initialize(NULL);

    if (SUCCEEDED(hr))
      hr = frame->SetPixelFormat(&format_guid);

    if (SUCCEEDED(hr))
      hr = frame->SetSize(width, height);

    if (SUCCEEDED(hr))
      hr = frame->WriteSource(wic_bitmap_, 0);

    if (SUCCEEDED(hr))
      hr = frame->Commit();

    if (SUCCEEDED(hr))
      hr = encoder->Commit();

    if (frame) frame->Release();
    if (encoder) encoder->Release();
    if (stream) stream->Release();
    if (factory) factory->Release();

    return SUCCEEDED(hr) ? CVS_S_Ok : CVS_E_Fail;
  }
  IWICImagingFactory* D2DSurface::create_wic_factory() const {
    IWICImagingFactory *factory = 0;
    auto hr = CoCreateInstance(CLSID_WICImagingFactory
        , NULL
        , CLSCTX_INPROC_SERVER
        , IID_IWICImagingFactory
        , (LPVOID*)&factory);
    if (FAILED(hr) || !factory) {
      return 0;
    }
    return factory;
  }
  Result D2DSurface::create_wic_bitmap(IWICImagingFactory *factory, IWICBitmapSource *source) {
    if (!factory || !source) return CVS_E_InvalidParam;
    
    WICPixelFormatGUID pixel_format;
    auto hr = source->GetPixelFormat(&pixel_format);

    if (Failed(hr)) return CVS_E_Fail;

    Result rst = CVS_E_Fail;
    IWICBitmapSource *new_source = 0;
    if (!IsEqualGUID(pixel_format, GUID_WICPixelFormat32bppPBGRA)) {
      hr = WICConvertBitmapSource(GUID_WICPixelFormat32bppPBGRA, source, &new_source);
      if (SUCCEEDED(hr)) {
        source = new_source;
      }
    }

    if (source) {
      UINT width, height;
      hr = source->GetSize(&width, &height);

      // create bitmap
      if (SUCCEEDED(hr)) {
        hr = factory->CreateBitmap(width, height
            , GUID_WICPixelFormat32bppPBGRA
            , WICBitmapCreateCacheOption::WICBitmapCacheOnDemand
            , &wic_bitmap_);
      }
      
      // copy data
      IWICBitmapLock *lock = 0;
      if (SUCCEEDED(hr)) {
        WICRect rc = { 0, 0, (INT)width, (INT)height };
        hr = wic_bitmap_->Lock(&rc, WICBitmapLockWrite, &lock);
      }

      UINT buffer_size = 0;
      WICInProcPointer pbuf = 0;
      if (SUCCEEDED(hr)) {
        hr = lock->GetDataPointer(&buffer_size, &pbuf);
      }

      if (SUCCEEDED(hr)) {
        UINT stride = buffer_size / height;
        hr = source->CopyPixels(0, stride, buffer_size, pbuf);
      }

      rst = SUCCEEDED(hr) ? CVS_S_Ok : CVS_E_Fail;

      if (lock) lock->Release();
    }

    if (new_source) new_source->Release();

    return rst;
  }

  Size D2DSurface::size() const {
    if (wic_bitmap_) {
      UINT w, h;
      auto hr = wic_bitmap_->GetSize(&w, &h);
      if (SUCCEEDED(hr)) return Size(w, h);
    }
    return Size();
  }
  PixelFormat D2DSurface::format() const { return PixelFormat::PBGRA32; }
  uint D2DSurface::buffer_size() const {
    if (wic_bitmap_) {
      UINT w, h;
      auto hr = wic_bitmap_->GetSize(&w, &h);
      if (SUCCEEDED(hr)) return w * h * 4;
    }
    return 0;
  }
  Result D2DSurface::copy_pixels(uint stride, uint buffer_size, void *pixels) const {
    if (!pixels) return CVS_E_InvalidParam;
    if (!wic_bitmap_) return CVS_E_Fail;
    auto hr = wic_bitmap_->CopyPixels(0, stride, buffer_size, (BYTE*)pixels);
    return SUCCEEDED(hr) ? CVS_S_Ok : CVS_E_Fail;
  }
  Result D2DSurface::data(ISurfaceData **surface_data) {
    if (!surface_data) return CVS_E_InvalidParam;
    D2DSurfaceData *s = new D2DSurfaceData(d2d_render());
    if (!s) return CVS_E_OutOfMemory;
    auto rst = s->create(wic_bitmap_);
    if (Failed(rst)) {
      SaveDecref(s);
      s = 0;
    }
    *surface_data = s;
    return rst;
  }

  IWICBitmap* D2DSurface::wic_bitmap() const { return wic_bitmap_; }
  REFWICPixelFormatGUID D2DSurface::wic_pixel_format(PixelFormat format) const {
    switch (format) {
      case PixelFormat::PBGRA32: return GUID_WICPixelFormat32bppPBGRA;
    }
    return GUID_WICPixelFormat32bppPBGRA;
  }
  GUID D2DSurface::wic_container_format(SurfaceType type) const {
    switch (type) {
      case SurfaceType::Bmp: return GUID_ContainerFormatBmp;
      case SurfaceType::Png: return GUID_ContainerFormatPng;
      case SurfaceType::Jpeg: return GUID_ContainerFormatJpeg;
    }
    return GUID_ContainerFormatBmp;
  }

  ///////////////////////////////////////////
  // D2DShape impl
  D2DShape::D2DShape(D2DRender *render) : D2DResource(render) {}
  D2DShape::~D2DShape() {}
  Result D2DShape::fill(ID2D1RenderTarget *target, ID2D1Brush *brush) const { return CVS_E_ShapeNotSupportFill; }
  Result D2DShape::draw(ID2D1RenderTarget *target, ID2D1Brush *brush, dtype stroke_width, ID2D1StrokeStyle *style) const { return CVS_E_ShapeNotSupportDraw; }

  // D2DShapeBaseForUsePath impl
  D2DShapeBaseForUsePath::D2DShapeBaseForUsePath(D2DRender *render) : D2DShape(render), d2d_path_(0) {}
  D2DShapeBaseForUsePath::~D2DShapeBaseForUsePath() { reset(); }
  Result D2DShapeBaseForUsePath::reset() {
    if (d2d_path_) {
      d2d_path_->Release();
      d2d_path_ = 0;
    }
    return CVS_S_Ok;
  }
  Result D2DShapeBaseForUsePath::fill(ID2D1RenderTarget *target, ID2D1Brush *brush) const {
    if (!target || !brush) return CVS_E_Fail;
    if (!d2d_path_) return CVS_E_Fail;

    target->FillGeometry(d2d_path_, brush);

    return CVS_S_Ok;
  }
  Result D2DShapeBaseForUsePath::draw(ID2D1RenderTarget *target, ID2D1Brush *brush, dtype stroke_width, ID2D1StrokeStyle *style) const {
    if (!target || !brush) return CVS_E_Fail;
    if (!d2d_path_) return CVS_E_Fail;

    target->DrawGeometry(d2d_path_, brush, stroke_width, style);

    return CVS_S_Ok;
  }
  Result D2DShapeBaseForUsePath::generate_d2d_path_geometry() {
    D2DShapeBaseForUsePath::reset();

    auto factory = d2d_factory();
    if (!factory) return CVS_E_Fail;

    HRESULT hr = factory->CreatePathGeometry(&d2d_path_);
    if (FAILED(hr)) return CVS_E_Fail;

    ID2D1GeometrySink *sink = 0;

    hr = d2d_path_->Open(&sink);
    if (SUCCEEDED(hr)) {
      d2d_path_paint(sink);
      hr = sink->Close();
    }

    if (sink)
      sink->Release();

    if (FAILED(hr)) {
      d2d_path_->Release();
      d2d_path_ = 0;
      return CVS_E_Fail;
    }
    return CVS_S_Ok;
  }

  // D2DShapeLine impl
  D2DShapeLine::D2DShapeLine(D2DRender *render) : D2DShape(render) {}
  D2DShapeLine::~D2DShapeLine() { reset(); }
  Result D2DShapeLine::draw(ID2D1RenderTarget *target, ID2D1Brush *brush, dtype stroke_width, ID2D1StrokeStyle *style) const {
    if (!target || !brush) return CVS_E_Fail;
    D2D_POINT_2F p1, p2;
    p1.x = pt1_.x; p1.y = pt1_.y;
    p2.x = pt2_.x; p2.y = pt2_.y;
    target->DrawLine(p1, p2, brush, stroke_width, style);
    return CVS_S_Ok;
  }
  
  // D2DShapePolyline impl
  D2DShapePolyline::D2DShapePolyline(D2DRender *render) : D2DShape(render) {}
  D2DShapePolyline::~D2DShapePolyline() { reset(); }
  Result D2DShapePolyline::draw(ID2D1RenderTarget *target, ID2D1Brush *brush, dtype stroke_width, ID2D1StrokeStyle *style) const {
    if (!target || !brush) return CVS_E_Fail;
    
    uint num = count();
    Point *pts = points();
    if (!pts || num < 2) return CVS_E_Fail;

    D2D_POINT_2F p1, p2;
    p1.x = pts[0].x; p1.y = pts[0].y;

    for (uint i = 1; i < num; ++i) {
      p2.x = pts[i].x; p2.y = pts[i].y;
      target->DrawLine(p1, p2, brush, stroke_width, style);
      p1 = p2;
    }

    return CVS_S_Ok;
  }
  
  // D2DShapeBezier impl
  D2DShapeBezier::D2DShapeBezier(D2DRender *render) : D2DShapeBaseForUsePath(render) {}
  D2DShapeBezier::~D2DShapeBezier() { reset(); }
  Result D2DShapeBezier::reset() {
    D2DShapeBaseForUsePath::reset();
    return ShapeBezier::reset();
  }
  Result D2DShapeBezier::set(const BezierSegment &beizer) {
    ShapeBezier::set(beizer);
    return generate_d2d_path_geometry();
  }
  Result D2DShapeBezier::set(const Point &pt0, const Point &pt1, const Point &pt2, const Point &pt3) {
    ShapeBezier::set(pt0, pt1, pt2, pt3);
    return generate_d2d_path_geometry();
  }
  void D2DShapeBezier::d2d_path_paint(ID2D1GeometrySink *sink) const {
    D2D_POINT_2F pt;
    D2D1_BEZIER_SEGMENT bezier;
    pt.x = pt0_.x; pt.y = pt0_.y;
    bezier.point1.x = pt1_.x; bezier.point1.y = pt1_.y;
    bezier.point2.x = pt2_.x; bezier.point2.y = pt2_.y;
    bezier.point3.x = pt3_.x; bezier.point3.y = pt3_.y;
    sink->BeginFigure(pt, D2D1_FIGURE_BEGIN::D2D1_FIGURE_BEGIN_FILLED);
    sink->AddBezier(bezier);
    sink->EndFigure(D2D1_FIGURE_END::D2D1_FIGURE_END_OPEN);
  }

  // D2DShapeArc impl
  D2DShapeArc::D2DShapeArc(D2DRender *render) : D2DShapeBaseForUsePath(render) {}
  D2DShapeArc::~D2DShapeArc() { reset(); }
  Result D2DShapeArc::reset() {
    D2DShapeBaseForUsePath::reset();
    return ShapeArc::reset();
  }
  Result D2DShapeArc::set(const ArcSegment &arc) {
    ShapeArc::set(arc);
    return generate_d2d_path_geometry();
  }
  Result D2DShapeArc::set(const Point &start, const Point &end, const Size &size, dtype angle, bool sweep, bool large) {
    ShapeArc::set(start, end, size, angle, sweep, large);
    return generate_d2d_path_geometry();
  }
  void D2DShapeArc::d2d_path_paint(ID2D1GeometrySink *sink) const {
    D2D_POINT_2F pt;
    D2D1_ARC_SEGMENT arc;
    pt.x = start_.x; pt.y = start_.y;
    arc.point.x = end_.x; arc.point.y = end_.y;
    arc.size.width = size_.w; arc.size.height = size_.h;
    arc.rotationAngle = angle_;
    arc.sweepDirection = sweep_
        ? D2D1_SWEEP_DIRECTION::D2D1_SWEEP_DIRECTION_CLOCKWISE
        : D2D1_SWEEP_DIRECTION::D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE;
    arc.arcSize = large_
        ? D2D1_ARC_SIZE::D2D1_ARC_SIZE_LARGE
        : D2D1_ARC_SIZE::D2D1_ARC_SIZE_SMALL;
    sink->BeginFigure(pt, D2D1_FIGURE_BEGIN::D2D1_FIGURE_BEGIN_FILLED);
    sink->AddArc(arc);
    sink->EndFigure(D2D1_FIGURE_END::D2D1_FIGURE_END_OPEN);
  }
  
  // D2DShapePolygon impl
  D2DShapePolygon::D2DShapePolygon(D2DRender *render) : D2DShapeBaseForUsePath(render) {}
  D2DShapePolygon::~D2DShapePolygon() { reset(); }
  Result D2DShapePolygon::reset() {
    D2DShapeBaseForUsePath::reset();
    return ShapePolygon::reset();
  }
  Result D2DShapePolygon::set(const Point *pts, uint count) {
    ShapePolygon::set(pts, count);
    return generate_d2d_path_geometry();
  }
  void D2DShapePolygon::d2d_path_paint(ID2D1GeometrySink *sink) const {
    uint num = count();
    Point *pts = points();
    if (!pts || num < 2) return;

    D2D_POINT_2F pt;
    pt.x = pts[0].x; pt.y = pts[0].y;

    sink->BeginFigure(pt, D2D1_FIGURE_BEGIN::D2D1_FIGURE_BEGIN_FILLED);

    for (uint i = 1; i < num; ++i) {
      pt.x = pts[i].x; pt.y = pts[i].y;
      sink->AddLine(pt);
    }

    sink->EndFigure(D2D1_FIGURE_END::D2D1_FIGURE_END_CLOSED);
  }
  
  // D2DShapeRect impl
  D2DShapeRect::D2DShapeRect(D2DRender *render) : D2DShape(render) {}
  D2DShapeRect::~D2DShapeRect() { reset(); }
  Result D2DShapeRect::fill(ID2D1RenderTarget *target, ID2D1Brush *brush) const {
    if (!target || !brush) return CVS_E_Fail;

    D2D1_ROUNDED_RECT rc;
    rc.rect.left = rect_.l();
    rc.rect.top = rect_.t();
    rc.rect.right = rect_.r();
    rc.rect.bottom = rect_.b();
    rc.radiusX = radius_.w;
    rc.radiusY = radius_.h;
    target->FillRoundedRectangle(rc, brush);

    return CVS_S_Ok;
  }
  Result D2DShapeRect::draw(ID2D1RenderTarget *target, ID2D1Brush *brush, dtype stroke_width, ID2D1StrokeStyle *style) const {
    if (!target || !brush) return CVS_E_Fail;

    D2D1_ROUNDED_RECT rc;
    rc.rect.left = rect_.l();
    rc.rect.top = rect_.t();
    rc.rect.right = rect_.r();
    rc.rect.bottom = rect_.b();
    rc.radiusX = radius_.w;
    rc.radiusY = radius_.h;
    target->DrawRoundedRectangle(rc, brush, stroke_width, style);

    return CVS_S_Ok;
  }
  
  // D2DShapeCircle impl
  D2DShapeCircle::D2DShapeCircle(D2DRender *render) : D2DShape(render) {}
  D2DShapeCircle::~D2DShapeCircle() { reset(); }
  Result D2DShapeCircle::fill(ID2D1RenderTarget *target, ID2D1Brush *brush) const {
    if (!target || !brush) return CVS_E_Fail;

    D2D1_ELLIPSE ellipse;
    ellipse.point.x = center_.x;
    ellipse.point.y = center_.y;
    ellipse.radiusX = radius_;
    ellipse.radiusY = radius_;
    target->FillEllipse(ellipse, brush);

    return CVS_S_Ok;
  }
  Result D2DShapeCircle::draw(ID2D1RenderTarget *target, ID2D1Brush *brush, dtype stroke_width, ID2D1StrokeStyle *style) const {
    if (!target || !brush) return CVS_E_Fail;

    D2D1_ELLIPSE ellipse;
    ellipse.point.x = center_.x;
    ellipse.point.y = center_.y;
    ellipse.radiusX = radius_;
    ellipse.radiusY = radius_;
    target->DrawEllipse(ellipse, brush, stroke_width, style);

    return CVS_S_Ok;
  }

  // D2DShapeEllipse impl
  D2DShapeEllipse::D2DShapeEllipse(D2DRender *render) : D2DShape(render) {}
  D2DShapeEllipse::~D2DShapeEllipse() { reset(); }
  Result D2DShapeEllipse::fill(ID2D1RenderTarget *target, ID2D1Brush *brush) const {
    if (!target || !brush) return CVS_E_Fail;

    D2D1_ELLIPSE ellipse;
    ellipse.point.x = center_.x;
    ellipse.point.y = center_.y;
    ellipse.radiusX = radius_.w;
    ellipse.radiusY = radius_.h;
    target->FillEllipse(ellipse, brush);

    return CVS_S_Ok;
  }
  Result D2DShapeEllipse::draw(ID2D1RenderTarget *target, ID2D1Brush *brush, dtype stroke_width, ID2D1StrokeStyle *style) const {
    if (!target || !brush) return CVS_E_Fail;

    D2D1_ELLIPSE ellipse;
    ellipse.point.x = center_.x;
    ellipse.point.y = center_.y;
    ellipse.radiusX = radius_.w;
    ellipse.radiusY = radius_.h;
    target->DrawEllipse(ellipse, brush, stroke_width, style);

    return CVS_S_Ok;
  }

  // D2DShapePath impl
  D2DShapePath::D2DShapePath(D2DRender *render) : D2DShape(render), d2d_path_(0), d2d_sink_(0) {}
  D2DShapePath::~D2DShapePath() { reset(); }
  Result D2DShapePath::fill(ID2D1RenderTarget *target, ID2D1Brush *brush) const {
    if (!target || !brush || !d2d_path_) return CVS_E_Fail;

    target->FillGeometry(d2d_path_, brush);

    return CVS_S_Ok;
  }
  Result D2DShapePath::draw(ID2D1RenderTarget *target, ID2D1Brush *brush, dtype stroke_width, ID2D1StrokeStyle *style) const {
    if (!target || !brush || !d2d_path_) return CVS_E_Fail;

    target->DrawGeometry(d2d_path_, brush, stroke_width, style);

    return CVS_S_Ok;
  }
  Result D2DShapePath::reset() {
    if (d2d_sink_) {
      d2d_sink_->Release();
      d2d_sink_ = 0;
    }
    if (d2d_path_) {
      d2d_path_->Release();
      d2d_path_ = 0;
    }
    return CVS_S_Ok;
  }

  Result D2DShapePath::open(const FillMode mode) {
    if (!d2d_path_) return CVS_E_RenderPathError;
    HRESULT hr = d2d_path_->Open(&d2d_sink_);
    if (SUCCEEDED(hr)) {
      d2d_sink_->SetFillMode(d2d_fill_mode(mode));
      return CVS_S_Ok;
    }
    return CVS_E_Fail;
  }
  Result D2DShapePath::close() {
    if (d2d_sink_) {
      d2d_sink_->Close();
      d2d_sink_->Release();
      d2d_sink_ = 0;
    }
    return CVS_S_Ok;
  }

  Result D2DShapePath::begin_figure(const Point &start_point) {
    if (!d2d_sink_) return CVS_E_RenderPathNotOpen;
    D2D1_POINT_2F pt;
    pt.x = start_point.x;
    pt.y = start_point.y;
    d2d_sink_->BeginFigure(pt, D2D1_FIGURE_BEGIN::D2D1_FIGURE_BEGIN_FILLED);
    return CVS_S_Ok;
  }
  Result D2DShapePath::end_figure(const FigureEnd figure_end) {
    if (!d2d_sink_) return CVS_E_RenderPathNotOpen;
    d2d_sink_->EndFigure(d2d_figure_end(figure_end));
    return CVS_S_Ok;
  }

  Result D2DShapePath::add_line(const Point &point) {
    if (!d2d_sink_) return CVS_E_RenderPathNotOpen;
    D2D1_POINT_2F pt;
    pt.x = point.x;
    pt.y = point.y;
    d2d_sink_->AddLine(pt);
    return CVS_S_Ok;
  }
  Result D2DShapePath::add_lines(const Point *points, uint count) {
    if (!d2d_sink_) return CVS_E_RenderPathNotOpen;
    if (!points || count == 0) return CVS_E_InvalidParam;

    D2D1_POINT_2F pt;

    for (uint i = 0; i < count; ++i) {
      pt.x = points[i].x;
      pt.y = points[i].y;
      d2d_sink_->AddLine(pt);
    }

    return CVS_S_Ok;
  }
  Result D2DShapePath::add_bezier(const Point &pt1, const Point &pt2, const Point &pt3) {
    if (!d2d_sink_) return CVS_E_RenderPathNotOpen;
    
    D2D1_BEZIER_SEGMENT bezier;
    bezier.point1.x = pt1.x; bezier.point1.y = pt1.y;
    bezier.point2.x = pt2.x; bezier.point2.y = pt2.y;
    bezier.point3.x = pt3.x; bezier.point3.y = pt3.y;
    d2d_sink_->AddBezier(bezier);
    
    return CVS_S_Ok;
  }
  Result D2DShapePath::add_arc(const Point &point, const Size &size, dtype angle, bool sweep, bool large) {
    if (!d2d_sink_) return CVS_E_RenderPathNotOpen;

    D2D1_ARC_SEGMENT arc;
    arc.point.x = point.x; arc.point.y = point.y;
    arc.size.width = size.w; arc.size.height = size.h;
    arc.rotationAngle = angle;
    arc.sweepDirection = sweep
        ? D2D1_SWEEP_DIRECTION::D2D1_SWEEP_DIRECTION_CLOCKWISE
        : D2D1_SWEEP_DIRECTION::D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE;
    arc.arcSize = large
        ? D2D1_ARC_SIZE::D2D1_ARC_SIZE_LARGE
        : D2D1_ARC_SIZE::D2D1_ARC_SIZE_SMALL;
    d2d_sink_->AddArc(arc);

    return CVS_S_Ok;
  }

  bool D2DShapePath::point_in_path(const Point &point) const {
    if (!d2d_path_) return false;
    return false;
  }

  ID2D1PathGeometry* D2DShapePath::d2d_path_geometry() const { return d2d_path_; }
  Result D2DShapePath::create() {
    reset();
    ID2D1Factory *factory = d2d_factory();
    if (!factory) return CVS_E_Fail;
    HRESULT hr = factory->CreatePathGeometry(&d2d_path_);
    return SUCCEEDED(hr) ? CVS_S_Ok : CVS_E_Fail;
  }

  D2D1_FILL_MODE D2DShapePath::d2d_fill_mode(const FillMode mode) const {
    switch (mode) {
      case FillMode::EvenOdd: return D2D1_FILL_MODE::D2D1_FILL_MODE_ALTERNATE;
      case FillMode::NoneZero: return D2D1_FILL_MODE::D2D1_FILL_MODE_WINDING;
    }
    return D2D1_FILL_MODE::D2D1_FILL_MODE_ALTERNATE;
  }
  D2D1_FIGURE_END D2DShapePath::d2d_figure_end(const FigureEnd figure_end) const {
    switch (figure_end) {
      case FigureEnd::Open: return D2D1_FIGURE_END::D2D1_FIGURE_END_OPEN;
      case FigureEnd::Close: return D2D1_FIGURE_END::D2D1_FIGURE_END_CLOSED;
    }
    return D2D1_FIGURE_END::D2D1_FIGURE_END_OPEN;
  }

  // D2DShapeText impl
  D2DShapeText::D2DShapeText(D2DRender *render) : D2DShape(render), dwrite_layout_(0) {}
  D2DShapeText::~D2DShapeText() { reset(); }
  Result D2DShapeText::fill(ID2D1RenderTarget *target, ID2D1Brush *brush) const {
    if (!target || !brush) return CVS_E_Fail;

    for (auto g : texts_)
      target->FillGeometry(g, brush);
    for (auto g : underlines_)
      target->FillGeometry(g, brush);
    for (auto g : strikethroughs_)
      target->FillGeometry(g, brush);

    return CVS_S_Ok;
  }
  Result D2DShapeText::draw(ID2D1RenderTarget *target, ID2D1Brush *brush, dtype stroke_width, ID2D1StrokeStyle *style) const {
    if (!target || !brush) return CVS_E_Fail;

    for (auto g : texts_)
      target->DrawGeometry(g, brush, stroke_width, style);
    for (auto g : underlines_)
      target->DrawGeometry(g, brush, stroke_width, style);
    for (auto g : strikethroughs_)
      target->DrawGeometry(g, brush, stroke_width, style);

    return CVS_S_Ok;
  }
  Result D2DShapeText::reset() {
    for (auto g : texts_)
      g->Release();
    texts_.clear();
    for (auto g : underlines_)
      g->Release();
    underlines_.clear();
    for (auto g : strikethroughs_)
      g->Release();
    strikethroughs_.clear();

    if (dwrite_layout_) {
      dwrite_layout_->Release();
      dwrite_layout_ = 0;
    }
    return ShapeText::reset();
  }
  Result D2DShapeText::set(const wchar_t *text, uint len, const Rect &rect
          , const FontFormat &font, const TextFormat &format) {
    reset();
    ShapeText::set(text, len, rect, font, format);

    Result rst = CVS_S_Ok;

    auto r = text_render();
    if (!r) rst = CVS_E_Fail;

    if (Succeeded(rst))
      rst = generate_dwrite_text_layout();
    
    if (Succeeded(rst)) {
      auto hr = r->BeginDraw();
      if (SUCCEEDED(hr))
        hr = dwrite_layout_->Draw(0, r, rect_.x, rect_.y);
      if (SUCCEEDED(hr))
        hr = r->Geometry(&texts_, &underlines_, &strikethroughs_);
      if (SUCCEEDED(hr))
        hr = r->EndDraw();
        
      rst = SUCCEEDED(hr) ? CVS_S_Ok : CVS_E_Fail;
    }

    if (Failed(rst))
      reset();
    return rst;
  }
  Result D2DShapeText::generate_dwrite_text_layout() {
    IDWriteFactory *f = dwrite_factory();
    if (!f) return CVS_E_Fail;

    IDWriteTextFormat *text_format = 0;
    auto hr = f->CreateTextFormat(font_.family
          ? font_.family : L"", 0
        , font_.bold ? DWRITE_FONT_WEIGHT::DWRITE_FONT_WEIGHT_BOLD
              : DWRITE_FONT_WEIGHT::DWRITE_FONT_WEIGHT_NORMAL
        , font_.italic ? DWRITE_FONT_STYLE::DWRITE_FONT_STYLE_ITALIC
              : DWRITE_FONT_STYLE::DWRITE_FONT_STYLE_NORMAL
        , DWRITE_FONT_STRETCH::DWRITE_FONT_STRETCH_NORMAL
        , font_.size, L"", &text_format);

    if (FAILED(hr)) return CVS_E_Fail;

    text_format->SetWordWrapping(format_.word_wrap
         ? DWRITE_WORD_WRAPPING::DWRITE_WORD_WRAPPING_WRAP
         : DWRITE_WORD_WRAPPING::DWRITE_WORD_WRAPPING_NO_WRAP);
    text_format->SetParagraphAlignment(get_align(format_.valign));
    text_format->SetTextAlignment(get_align(format_.halign));
    text_format->SetReadingDirection(get_reading_direction(format_.direction));

    if (format_.auto_width || format_.auto_height
          || rect_.w <= 0.f || rect_.h <= 0.f) {
      IDWriteTextLayout *layout = 0;
      hr = f->CreateTextLayout(text_, len_, text_format
        , 0.f, 0.f, &layout);
      if (SUCCEEDED(hr)) {
        DWRITE_TEXT_METRICS metrics;
        hr = layout->GetMetrics(&metrics);
        if (SUCCEEDED(hr)) {
          if (format_.auto_width || rect_.w <= 0.f)
            rect_.w = metrics.widthIncludingTrailingWhitespace;
          if (format_.auto_height || rect_.h <= 0.f)
            rect_.h = metrics.height;
        }
        layout->Release();
      }
    }

    // trimming
    {
      DWRITE_TRIMMING trim;
      trim.granularity = DWRITE_TRIMMING_GRANULARITY::DWRITE_TRIMMING_GRANULARITY_CHARACTER;
      trim.delimiter = 1;
      trim.delimiterCount = 10;
      IDWriteInlineObject *inline_obj = 0;

      switch (format_.trimming) {
        case TextTrimming::Ellipsis: {
          f->CreateEllipsisTrimmingSign(text_format, &inline_obj);
          text_format->SetTrimming(&trim, inline_obj);
        } break;
        case TextTrimming::Clip: {
          text_format->SetTrimming(&trim, inline_obj);
        } break;
        default: break;
      }

      if (inline_obj) inline_obj->Release();
    }
    

    hr = f->CreateTextLayout(text_, len_, text_format
        , rect_.w, rect_.h, &dwrite_layout_);
    if (text_format) text_format->Release();

    if (SUCCEEDED(hr)) {
      DWRITE_TEXT_RANGE text_range;
      text_range.startPosition = 0;
      text_range.length = len_;
      dwrite_layout_->SetUnderline(font_.underline, text_range);
      dwrite_layout_->SetStrikethrough(font_.strikethrough, text_range);
    }

    if (FAILED(hr)) return CVS_E_Fail;

    return CVS_S_Ok;
  }
  DWRITE_READING_DIRECTION D2DShapeText::get_reading_direction(TextDirection d) const {
    switch (d) {
      case TextDirection::LeftToRight: return DWRITE_READING_DIRECTION::DWRITE_READING_DIRECTION_LEFT_TO_RIGHT;
      case TextDirection::RightToLeft: return DWRITE_READING_DIRECTION::DWRITE_READING_DIRECTION_RIGHT_TO_LEFT;
    }
    return DWRITE_READING_DIRECTION::DWRITE_READING_DIRECTION_LEFT_TO_RIGHT;
  }
  DWRITE_PARAGRAPH_ALIGNMENT D2DShapeText::get_align(TextAlignVer d) const {
    switch (d) {
      case TextAlignVer::Top: return DWRITE_PARAGRAPH_ALIGNMENT::DWRITE_PARAGRAPH_ALIGNMENT_NEAR;
      case TextAlignVer::Middle: return DWRITE_PARAGRAPH_ALIGNMENT::DWRITE_PARAGRAPH_ALIGNMENT_CENTER;
      case TextAlignVer::Bottom: return DWRITE_PARAGRAPH_ALIGNMENT::DWRITE_PARAGRAPH_ALIGNMENT_FAR;
    }
    return DWRITE_PARAGRAPH_ALIGNMENT::DWRITE_PARAGRAPH_ALIGNMENT_NEAR;
  }
  DWRITE_TEXT_ALIGNMENT D2DShapeText::get_align(TextAlignHor d) const {
    switch (d) {
      case TextAlignHor::Left: return DWRITE_TEXT_ALIGNMENT::DWRITE_TEXT_ALIGNMENT_LEADING;
      case TextAlignHor::Center: return DWRITE_TEXT_ALIGNMENT::DWRITE_TEXT_ALIGNMENT_CENTER;
      case TextAlignHor::Right: return DWRITE_TEXT_ALIGNMENT::DWRITE_TEXT_ALIGNMENT_TRAILING;
    }
    return DWRITE_TEXT_ALIGNMENT::DWRITE_TEXT_ALIGNMENT_LEADING;
  }

  ///////////////////////////////////////////
  // D2DStrokeStyle impl
  D2DStrokeStyle::D2DStrokeStyle(D2DRender *render) : D2DResource(render), d2d_stroke_style_(0) {}
  D2DStrokeStyle::~D2DStrokeStyle() { reset(); }
  Result D2DStrokeStyle::reset() {
    if (d2d_stroke_style_) {
      d2d_stroke_style_->Release();
      d2d_stroke_style_ = 0;
    }
    return CVS_S_Ok;
  }
  LineCap D2DStrokeStyle::start_line_cap() const {
    D2D1_CAP_STYLE cap = D2D1_CAP_STYLE::D2D1_CAP_STYLE_FLAT;
    if (d2d_stroke_style_)
      cap = d2d_stroke_style_->GetStartCap();
    return cap_style(cap);
  }
  LineCap D2DStrokeStyle::end_line_cap() const {
    D2D1_CAP_STYLE cap = D2D1_CAP_STYLE::D2D1_CAP_STYLE_FLAT;
    if (d2d_stroke_style_)
      cap = d2d_stroke_style_->GetEndCap();
    return cap_style(cap);
  }
  LineCap D2DStrokeStyle::dash_line_cap() const {
    D2D1_CAP_STYLE cap = D2D1_CAP_STYLE::D2D1_CAP_STYLE_FLAT;
    if (d2d_stroke_style_)
      cap = d2d_stroke_style_->GetDashCap();
    return cap_style(cap);
  }
  LineJoin D2DStrokeStyle::line_join() const {
    D2D1_LINE_JOIN join = D2D1_LINE_JOIN::D2D1_LINE_JOIN_MITER;
    if (d2d_stroke_style_)
      join = d2d_stroke_style_->GetLineJoin();
    return line_join(join);
  }
  dtype D2DStrokeStyle::miterlimit() const {
    if (!d2d_stroke_style_) return 0.f;
    return d2d_stroke_style_->GetMiterLimit();
  }
  dtype D2DStrokeStyle::dash_offset() const {
    if (!d2d_stroke_style_) return 0.f;
    return d2d_stroke_style_->GetDashOffset();
  }
  uint D2DStrokeStyle::dashes_count() const {
    if (!d2d_stroke_style_) return 0;
    return d2d_stroke_style_->GetDashesCount();
  }
  Result D2DStrokeStyle::dashes_data(dtype *dashes, uint count) const {
    if (!dashes || count == 0) return CVS_E_InvalidParam;
    if (!d2d_stroke_style_) return CVS_E_Fail;
    FLOAT *d = new FLOAT[count];
    if (!d) return CVS_E_OutOfMemory;
    d2d_stroke_style_->GetDashes(d, count);
    for (uint i = 0; i < count; ++i)
      dashes[i] = d[i];
    delete[] d;
    return CVS_S_Ok;
  }

  ID2D1StrokeStyle* D2DStrokeStyle::d2d_stroke_style() const { return d2d_stroke_style_; }

  D2D1_CAP_STYLE D2DStrokeStyle::d2d_cap_style(LineCap cap) const {
    switch (cap) {
      case LineCap::Flat: return D2D1_CAP_STYLE::D2D1_CAP_STYLE_FLAT;
      case LineCap::Round: return D2D1_CAP_STYLE::D2D1_CAP_STYLE_ROUND;
      case LineCap::Square: return D2D1_CAP_STYLE::D2D1_CAP_STYLE_SQUARE;
      case LineCap::Triangle: return D2D1_CAP_STYLE::D2D1_CAP_STYLE_TRIANGLE;
    }
    return D2D1_CAP_STYLE::D2D1_CAP_STYLE_FLAT;
  }
  D2D1_LINE_JOIN D2DStrokeStyle::d2d_line_join(LineJoin join) const {
    switch (join) {
      case LineJoin::Miter: return D2D1_LINE_JOIN::D2D1_LINE_JOIN_MITER;
      case LineJoin::Bevel: return D2D1_LINE_JOIN::D2D1_LINE_JOIN_BEVEL;
      case LineJoin::Round: return D2D1_LINE_JOIN::D2D1_LINE_JOIN_ROUND;
    }
    return D2D1_LINE_JOIN::D2D1_LINE_JOIN_MITER;
  }
  LineCap D2DStrokeStyle::cap_style(D2D1_CAP_STYLE cap) const {
    switch (cap) {
      case D2D1_CAP_STYLE::D2D1_CAP_STYLE_FLAT: return LineCap::Flat;
      case D2D1_CAP_STYLE::D2D1_CAP_STYLE_ROUND: return LineCap::Round;
      case D2D1_CAP_STYLE::D2D1_CAP_STYLE_SQUARE: return LineCap::Square;
      case D2D1_CAP_STYLE::D2D1_CAP_STYLE_TRIANGLE: return LineCap::Triangle;
    }
    return LineCap::Flat;
  }
  LineJoin D2DStrokeStyle::line_join(D2D1_LINE_JOIN join) const {
    switch (join) {
      case D2D1_LINE_JOIN::D2D1_LINE_JOIN_MITER: return LineJoin::Miter;
      case D2D1_LINE_JOIN::D2D1_LINE_JOIN_BEVEL: return LineJoin::Bevel;
      case D2D1_LINE_JOIN::D2D1_LINE_JOIN_ROUND: return LineJoin::Round;
    }
    return LineJoin::Miter;
  }
  
  Result D2DStrokeStyle::create(const StrokeStyleProperties &props, const dtype *dashs, uint count) {
    ID2D1Factory *f = d2d_factory();
    if (!f) return CVS_E_Fail;

    reset();

    D2D1_STROKE_STYLE_PROPERTIES p;
    p.startCap = d2d_cap_style(props.startCap);
    p.endCap = d2d_cap_style(props.startCap);
    p.dashCap = d2d_cap_style(props.startCap);
    p.lineJoin = d2d_line_join(props.lineJoin);
    p.miterLimit = props.miterLimit;
    p.dashStyle = D2D1_DASH_STYLE::D2D1_DASH_STYLE_SOLID;
    p.dashOffset = props.dashOffset;
    
    HRESULT hr = S_OK;
    if (dashs && count > 0) {
      FLOAT *d = new FLOAT[count];
      if (!d) return CVS_E_OutOfMemory;
      
      for (uint i = 0; i < count; ++i)
        d[i] = dashs[i];
    
      p.dashStyle = D2D1_DASH_STYLE::D2D1_DASH_STYLE_CUSTOM;

      hr = f->CreateStrokeStyle(p, d, count, &d2d_stroke_style_);
      
      delete[] d;
    } else {
      hr = f->CreateStrokeStyle(p, 0, 0, &d2d_stroke_style_);
    }
    
    return SUCCEEDED(hr) ? CVS_S_Ok : CVS_E_Fail;
  }

  ///////////////////////////////////////////
  // D2DBrush impl
  D2DBrush::D2DBrush(D2DRender *render, D2DTarget *target) : D2DTargetResource(render, target) {}
  D2DBrush::~D2DBrush() {}
  dtype D2DBrush::opacity() const {
    ID2D1Brush *brush = d2d_brush();
    if (!brush) return 1.f;
    return brush->GetOpacity();
  }
  Result D2DBrush::set_opacity(dtype _opacity) {
    ID2D1Brush *brush = d2d_brush();
    if (!brush) return CVS_E_RenderNoBrush;
    brush->SetOpacity(_opacity);
    return CVS_S_Ok;
  }
  Matrix D2DBrush::transform() const {
    ID2D1Brush *brush = d2d_brush();
    if (!brush) return Matrix();
    D2D1_MATRIX_3X2_F m;
    brush->GetTransform(&m);
    return Matrix(
          m._11, m._12
        , m._21, m._22
        , m._31, m._32);
  }
  Result D2DBrush::set_transform(const Matrix &m) {
    ID2D1Brush *brush = d2d_brush();
    if (!brush) return CVS_E_RenderNoBrush;

    D2D1_MATRIX_3X2_F matrix;
    matrix._11 = m.a; matrix._12 = m.b; matrix._21 = m.c;
    matrix._22 = m.d; matrix._31 = m.e; matrix._32 = m.f;
    brush->SetTransform(matrix);
    return CVS_S_Ok;
  }
  
  // D2DSolidColorBrush impl
  D2DSolidColorBrush::D2DSolidColorBrush(D2DRender *render, D2DTarget *target) : D2DBrush(render, target), d2d_brush_(0) {}
  D2DSolidColorBrush::~D2DSolidColorBrush() { reset(); }
  Result D2DSolidColorBrush::reset() {
    if (d2d_brush_) {
      d2d_brush_->Release();
      d2d_brush_ = 0;
    }
    return CVS_S_Ok;
  }
  Color D2DSolidColorBrush::color() const {
    Color c;
    if (d2d_brush_) {
      D2D1_COLOR_F cf = d2d_brush_->GetColor();
      c.a = cf.a;
      c.r = cf.r;
      c.g = cf.g;
      c.b = cf.b;
    }
    return std::move(c);
  }
  Result D2DSolidColorBrush::set_color(const Color &color) {
    if (!d2d_brush_) return CVS_E_RenderNoBrush;
    D2D1_COLOR_F cf;
    cf.a = color.a;
    cf.r = color.r;
    cf.g = color.g;
    cf.b = color.b;
    d2d_brush_->SetColor(cf);
    return CVS_S_Ok;
  }
  ID2D1Brush* D2DSolidColorBrush::d2d_brush() const { return d2d_brush_; }
  Result D2DSolidColorBrush::create(const Color &color) {
    reset();
    ID2D1RenderTarget *t = d2d_target();
    if (!t) return CVS_E_RenderNoTarget;

    D2D1_COLOR_F cf;
    cf.a = color.a;
    cf.r = color.r;
    cf.g = color.g;
    cf.b = color.b;

    HRESULT hr = t->CreateSolidColorBrush(cf, &d2d_brush_);

    return SUCCEEDED(hr) ? CVS_S_Ok : CVS_E_Fail;
  }

  // D2DGradientStopCollection impl
  D2DGradientStopCollection::D2DGradientStopCollection(D2DRender *render, D2DTarget *target)
      : D2DTargetResource(render, target), d2d_stop_collection_(0) {}
  D2DGradientStopCollection::~D2DGradientStopCollection() { reset(); }
  Result D2DGradientStopCollection::reset() {
    if (d2d_stop_collection_) {
      d2d_stop_collection_->Release();
      d2d_stop_collection_ = 0;
    }
    return CVS_S_Ok;
  }
  uint D2DGradientStopCollection::count() const {
    if (d2d_stop_collection_) return d2d_stop_collection_->GetGradientStopCount();
    return 0;
  }
  Result D2DGradientStopCollection::data(GradientStop *stops) const {
    if (!stops) return CVS_E_InvalidParam;
    if (!d2d_stop_collection_) return CVS_E_Fail;
    auto c = d2d_stop_collection_->GetGradientStopCount();
    if (c <= 0) return CVS_E_Fail;
    auto s = new D2D1_GRADIENT_STOP[c];
    if (!s) return CVS_E_OutOfMemory;
    d2d_stop_collection_->GetGradientStops(s, c);
    for (uint i = 0; i < c; ++i) {
      stops[i].color.a = s[i].color.a;
      stops[i].color.r = s[i].color.r;
      stops[i].color.g = s[i].color.g;
      stops[i].color.b = s[i].color.b;
      stops[i].pos = s[i].position;
    }
    delete[] s;
    return CVS_S_Ok;
  }
  ExtendMode D2DGradientStopCollection::extend_mode() const {
    if (d2d_stop_collection_) return get_extend_mode(d2d_stop_collection_->GetExtendMode());
    return ExtendMode::Clamp;
  }
  ID2D1GradientStopCollection* D2DGradientStopCollection::d2d_stop_collection() const { return d2d_stop_collection_; }
  D2D1_EXTEND_MODE D2DGradientStopCollection::get_extend_mode(ExtendMode mode) const {
    switch (mode) {
      case ExtendMode::Clamp: return D2D1_EXTEND_MODE::D2D1_EXTEND_MODE_CLAMP;
      case ExtendMode::Wrap: return D2D1_EXTEND_MODE::D2D1_EXTEND_MODE_WRAP;
      case ExtendMode::Mirror: return D2D1_EXTEND_MODE::D2D1_EXTEND_MODE_MIRROR;
    }
    return D2D1_EXTEND_MODE::D2D1_EXTEND_MODE_CLAMP;
  }
  ExtendMode D2DGradientStopCollection::get_extend_mode(D2D1_EXTEND_MODE mode) const {
    switch (mode) {
      case D2D1_EXTEND_MODE::D2D1_EXTEND_MODE_CLAMP: return ExtendMode::Clamp;
      case D2D1_EXTEND_MODE::D2D1_EXTEND_MODE_WRAP: return ExtendMode::Wrap;
      case D2D1_EXTEND_MODE::D2D1_EXTEND_MODE_MIRROR: return ExtendMode::Mirror;
    }
    return ExtendMode::Clamp;
  }
  Result D2DGradientStopCollection::create(const GradientStop *stops, uint count, ExtendMode mode) {
    if (!stops || count == 0) return CVS_E_InvalidParam;
    auto t = d2d_target();
    if (!t) return CVS_E_RenderNoTarget;

    D2D1_GRADIENT_STOP *s = new D2D1_GRADIENT_STOP[count];
    if (!s) return CVS_E_OutOfMemory;
    for (uint i = 0; i < count; ++i) {
      s[i].color.a = stops[i].color.a;
      s[i].color.r = stops[i].color.r;
      s[i].color.g = stops[i].color.g;
      s[i].color.b = stops[i].color.b;
      s[i].position = stops[i].pos;
    }
    reset();
    auto hr = t->CreateGradientStopCollection(s, count
        , D2D1_GAMMA::D2D1_GAMMA_2_2
        , get_extend_mode(mode)
        , &d2d_stop_collection_);
    delete[] s;

    return SUCCEEDED(hr) ? CVS_S_Ok : CVS_E_Fail;
  }

  // D2DLinearGradientBrush impl
  D2DLinearGradientBrush::D2DLinearGradientBrush(D2DRender *render, D2DTarget *target)
      : D2DBrush(render, target), d2d_brush_(0), stop_collection_(0) {}
  D2DLinearGradientBrush::~D2DLinearGradientBrush() { reset(); }
  Result D2DLinearGradientBrush::reset() {
    if (d2d_brush_) {
      d2d_brush_->Release();
      d2d_brush_ = 0;
    }
    SaveDecref(stop_collection_);
    stop_collection_ = 0;
    return CVS_S_Ok;
  }
  IGradientStopCollection* D2DLinearGradientBrush::stop_collection() const { return stop_collection_; }
  Point D2DLinearGradientBrush::start_point() const {
    if (d2d_brush_) {
      auto pt = d2d_brush_->GetStartPoint();
      return Point(pt.x, pt.y);
    }
    return Point();
  }
  Result D2DLinearGradientBrush::set_start_point(const Point& point) {
    if (!d2d_brush_) return CVS_E_Fail;
    D2D1_POINT_2F pt;
    pt.x = point.x;
    pt.y = point.y;
    d2d_brush_->SetStartPoint(pt);
    return CVS_S_Ok;
  }
  Point D2DLinearGradientBrush::end_point() const {
    if (d2d_brush_) {
      auto pt = d2d_brush_->GetEndPoint();
      return Point(pt.x, pt.y);
    }
    return Point();
  }
  Result D2DLinearGradientBrush::set_end_point(const Point& point) {
    if (!d2d_brush_) return CVS_E_Fail;
    D2D1_POINT_2F pt;
    pt.x = point.x;
    pt.y = point.y;
    d2d_brush_->SetEndPoint(pt);
    return CVS_S_Ok;
  }
  ID2D1Brush* D2DLinearGradientBrush::d2d_brush() const { return d2d_brush_; }
  Result D2DLinearGradientBrush::create(const LinearGradientBrushProperties &in_props, D2DGradientStopCollection *stop_collection) {
    if (!stop_collection) return CVS_E_InvalidParam;
    auto t = d2d_target();
    if (!t) return CVS_E_RenderNoTarget;
    reset();
    D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES props = { 0 };
    props.startPoint = D2D1::Point2F(in_props.start.x, in_props.start.y);
    props.endPoint = D2D1::Point2F(in_props.end.x, in_props.end.y);
    auto hr = t->CreateLinearGradientBrush(props, stop_collection->d2d_stop_collection(), &d2d_brush_);
    if (SUCCEEDED(hr)) {
      stop_collection_ = stop_collection;
      stop_collection_->incref();
    }
    return SUCCEEDED(hr) ? CVS_S_Ok : CVS_E_Fail;
  }

  // D2DRadialGradientBrush impl
  D2DRadialGradientBrush::D2DRadialGradientBrush(D2DRender *render, D2DTarget *target)
      : D2DBrush(render, target), d2d_brush_(0), stop_collection_(0) {}
  D2DRadialGradientBrush::~D2DRadialGradientBrush() { reset(); }
  Result D2DRadialGradientBrush::reset() {
    if (d2d_brush_) {
      d2d_brush_->Release();
      d2d_brush_ = 0;
    }
    SaveDecref(stop_collection_);
    stop_collection_ = 0;
    return CVS_S_Ok;
  }
  IGradientStopCollection* D2DRadialGradientBrush::stop_collection() const { return stop_collection_; }
  Point D2DRadialGradientBrush::center() const {
    if (d2d_brush_) {
      auto pt = d2d_brush_->GetCenter();
      return Point(pt.x, pt.y);
    }
    return Point();
  }
  Result D2DRadialGradientBrush::set_center(const Point& center) {
    if (!d2d_brush_) return CVS_E_Fail;
    D2D1_POINT_2F pt;
    pt.x = center.x;
    pt.y = center.y;
    d2d_brush_->SetCenter(pt);
    return CVS_S_Ok;
  }
  Point D2DRadialGradientBrush::offset() const {
    if (d2d_brush_) {
      auto pt = d2d_brush_->GetGradientOriginOffset();
      return Point(pt.x, pt.y);
    }
    return Point();
  }
  Result D2DRadialGradientBrush::set_offset(const Point& offset) {
    if (!d2d_brush_) return CVS_E_Fail;
    D2D1_POINT_2F pt;
    pt.x = offset.x;
    pt.y = offset.y;
    d2d_brush_->SetGradientOriginOffset(pt);
    return CVS_S_Ok;
  }
  dtype D2DRadialGradientBrush::radius_x() const {
    if (d2d_brush_) {
      auto radius = d2d_brush_->GetRadiusX();
      return radius;
    }
    return 0;
  }
  Result D2DRadialGradientBrush::set_radius_x(dtype radius_x) {
    if (!d2d_brush_) return CVS_E_Fail;
    d2d_brush_->SetRadiusX(radius_x);
    return CVS_S_Ok;
  }
  dtype D2DRadialGradientBrush::radius_y() const {
    if (d2d_brush_) {
      auto radius = d2d_brush_->GetRadiusY();
      return radius;
    }
    return 0;
  }
  Result D2DRadialGradientBrush::set_radius_y(dtype radius_y) {
    if (!d2d_brush_) return CVS_E_Fail;
    d2d_brush_->SetRadiusX(radius_y);
    return CVS_S_Ok;
  }
  ID2D1Brush* D2DRadialGradientBrush::d2d_brush() const { return d2d_brush_; }
  Result D2DRadialGradientBrush::create(const RadialGradientBrushProperties &in_props, D2DGradientStopCollection *stop_collection) {
    if (!stop_collection) return CVS_E_InvalidParam;
    auto t = d2d_target();
    if (!t) return CVS_E_RenderNoTarget;
    reset();
    D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES props = { 0 };
    props.center = D2D1::Point2F(in_props.center.x, in_props.center.y);
    props.gradientOriginOffset = D2D1::Point2F(in_props.offset.x, in_props.offset.y);
    props.radiusX = in_props.radius_x;
    props.radiusY = in_props.radius_y;
    auto hr = t->CreateRadialGradientBrush(props, stop_collection->d2d_stop_collection(), &d2d_brush_);
    if (SUCCEEDED(hr)) {
      stop_collection_ = stop_collection;
      stop_collection_->incref();
    }
    return SUCCEEDED(hr) ? CVS_S_Ok : CVS_E_Fail;
  }

  // D2DBitmapBrush impl
  D2DBitmapBrush::D2DBitmapBrush(D2DRender *render, D2DTarget *target) : D2DBrush(render, target), d2d_brush_(0), bitmap_(0) {}
  D2DBitmapBrush::~D2DBitmapBrush() { reset(); }
  Result D2DBitmapBrush::reset() {
    if (d2d_brush_) {
      d2d_brush_->Release();
      d2d_brush_ = 0;
    }
    SaveDecref(bitmap_);
    bitmap_ = 0;
    return CVS_S_Ok;
  }
  Result D2DBitmapBrush::bitmap(IBitmap **_bitmap) const {
    if (!_bitmap) return CVS_E_InvalidParam;
    if (!bitmap_) return CVS_E_RenderNoBrush;
    *_bitmap = bitmap_;
    return CVS_S_Ok;
  }
  Result D2DBitmapBrush::set_bitmap(IBitmap *_bitmap) {
    auto b = dynamic_cast<D2DBitmap*>(_bitmap);
    if (!b) return CVS_E_InvalidParam;
    if (bitmap_ == b) return CVS_S_RenderBrushAlreadyExist;
    reset();
    auto t = d2d_target();
    if (!t) return CVS_E_RenderNoTarget;
    auto hr = t->CreateBitmapBrush(b->d2d_bitmap(), &d2d_brush_);
    if (SUCCEEDED(hr)) {
      bitmap_ = b;
      bitmap_->incref();
    }
    return SUCCEEDED(hr) ? CVS_S_Ok : CVS_E_Fail;
  }
  ExtendMode D2DBitmapBrush::extend_mode_x() const {
    if (!d2d_brush_) return ExtendMode::Clamp;
    return get_extend_mode(d2d_brush_->GetExtendModeX());
  }
  ExtendMode D2DBitmapBrush::extend_mode_y() const {
    if (!d2d_brush_) return ExtendMode::Clamp;
    return get_extend_mode(d2d_brush_->GetExtendModeY());
  }
  Result D2DBitmapBrush::set_extend_mode_x(ExtendMode mode_x) {
    if (!d2d_brush_) return CVS_E_BitmapBrushNotSetBiamtp;
    d2d_brush_->SetExtendModeX(get_extend_mode(mode_x));
    return CVS_S_Ok;
  }
  Result D2DBitmapBrush::set_extend_mode_y(ExtendMode mode_y) {
    if (!d2d_brush_) return CVS_E_BitmapBrushNotSetBiamtp;
    d2d_brush_->SetExtendModeY(get_extend_mode(mode_y));
    return CVS_S_Ok;
  }
  ID2D1Brush* D2DBitmapBrush::d2d_brush() const { return d2d_brush_; }

  ExtendMode D2DBitmapBrush::get_extend_mode(D2D1_EXTEND_MODE mode) const {
    switch (mode) {
      case D2D1_EXTEND_MODE::D2D1_EXTEND_MODE_CLAMP: return ExtendMode::Clamp;
      case D2D1_EXTEND_MODE::D2D1_EXTEND_MODE_WRAP: return ExtendMode::Wrap;
      case D2D1_EXTEND_MODE::D2D1_EXTEND_MODE_MIRROR: return ExtendMode::Mirror;
    }
    return ExtendMode::Clamp;
  }
  D2D1_EXTEND_MODE D2DBitmapBrush::get_extend_mode(ExtendMode mode) const {
    switch (mode) {
      case ExtendMode::Clamp: return D2D1_EXTEND_MODE::D2D1_EXTEND_MODE_CLAMP;
      case ExtendMode::Wrap: return D2D1_EXTEND_MODE::D2D1_EXTEND_MODE_WRAP;
      case ExtendMode::Mirror: return D2D1_EXTEND_MODE::D2D1_EXTEND_MODE_MIRROR;
    }
    return D2D1_EXTEND_MODE::D2D1_EXTEND_MODE_CLAMP;
  }

  ///////////////////////////////////////////
  // D2DBitmap impl
  D2DBitmap::D2DBitmap(D2DRender *render, D2DTarget *target) : D2DTargetResource(render, target), d2d_bitmap_(0) {}
  D2DBitmap::~D2DBitmap() { reset(); }
  Result D2DBitmap::reset() {
    if (d2d_bitmap_) {
      d2d_bitmap_->Release();
      d2d_bitmap_ = 0;
    }
    return CVS_S_Ok;
  }
  Result D2DBitmap::create(ISurface *surface) {
    auto s = dynamic_cast<D2DSurface*>(surface);
    if (!s) return CVS_E_InvalidParam;

    auto t = d2d_target();
    if (!t) return CVS_E_RenderNoTarget;

    reset();

    HRESULT hr = t->CreateBitmapFromWicBitmap(s->wic_bitmap(), &d2d_bitmap_);
    return SUCCEEDED(hr) ? CVS_S_Ok : CVS_E_Fail;
  }
  Size D2DBitmap::size() const {
    if (d2d_bitmap_) {
      auto sz = d2d_bitmap_->GetSize();
      return Size(sz.width, sz.height);
    }
    return Size();
  }
  Result D2DBitmap::create(ID2D1Bitmap *bitmap) {
    if (!bitmap) return CVS_E_InvalidParam;
    reset();
    d2d_bitmap_ = bitmap;
    return CVS_S_Ok;
  }
  ID2D1Bitmap* D2DBitmap::d2d_bitmap() const { return d2d_bitmap_; }

  ///////////////////////////////////////////
  // D2DTarget impl
  D2DTarget::D2DTarget(D2DRender *render) : D2DResource(render) {}
  D2DTarget::~D2DTarget() {}
  Matrix D2DTarget::transform() const {
    Matrix rm;
    ID2D1RenderTarget *target = d2d_target();
    if (!target) return std::move(rm);

    D2D1_MATRIX_3X2_F m;
    target->GetTransform(&m);
    rm.a = m._11; rm.b = m._12; rm.c = m._21;
    rm.d = m._22; rm.e = m._31; rm.f = m._32;

    return std::move(rm);
  }
  Result D2DTarget::set_transform(const Matrix &matrix) {
    ID2D1RenderTarget *target = d2d_target();
    if (!target) return CVS_E_RenderNoTarget;

    D2D1_MATRIX_3X2_F m;
    m._11 = matrix.a; m._12 = matrix.b; m._21 = matrix.c;
    m._22 = matrix.d; m._31 = matrix.e; m._32 = matrix.f;
    target->SetTransform(m);

    return CVS_S_Ok;
  }
  Result D2DTarget::begin_draw() {
    ID2D1RenderTarget *target = d2d_target();
    if (!target) return CVS_E_RenderNoTarget;
    target->BeginDraw();
    return CVS_S_Ok;
  }
  Result D2DTarget::end_draw() {
    ID2D1RenderTarget *target = d2d_target();
    if (!target) return CVS_E_RenderNoTarget;
    HRESULT hr = target->EndDraw();
    return SUCCEEDED(hr) ? CVS_S_Ok : CVS_E_Fail;
  }
  Result D2DTarget::clear(const Color *color) {
    ID2D1RenderTarget *target = d2d_target();
    if (!target) return CVS_E_RenderNoTarget;
    if (!color) target->Clear();
    else {
      D2D1_COLOR_F c;
      c.a = color->a;
      c.r = color->r;
      c.g = color->g;
      c.b = color->b;
      target->Clear(c);
    }
    return CVS_S_Ok;
  }
  Result D2DTarget::fill_shape(IShape *shape, IBrush *brush) {
    auto s = dynamic_cast<D2DShape*>(shape);
    auto b = dynamic_cast<D2DBrush*>(brush);
    if (!s || !b) return CVS_E_InvalidParam;

    return s->fill(d2d_target(), b->d2d_brush());
  }
  Result D2DTarget::draw_shape(IShape *shape, IBrush *brush, dtype stroke_width, IStrokeStyle *stroke_style) {
    auto s = dynamic_cast<D2DShape*>(shape);
    auto b = dynamic_cast<D2DBrush*>(brush);
    if (!s || !b) return CVS_E_InvalidParam;
    auto ss = dynamic_cast<D2DStrokeStyle*>(stroke_style);

    return s->draw(d2d_target(), b->d2d_brush(), stroke_width
        , !!ss ? ss->d2d_stroke_style() : 0);
  }
  Result D2DTarget::draw_bitmap(IBitmap *bitmap, const Rect *dst, const Rect *src) {
    auto b = dynamic_cast<D2DBitmap*>(bitmap);
    if (!b) return CVS_E_InvalidParam;
    auto t = d2d_target();
    if (!t) return CVS_E_RenderNoTarget;

    D2D_RECT_F d, s;
    if (dst) {
      d.left = dst->l();
      d.top = dst->t();
      d.right = dst->r();
      d.bottom = dst->b();
    }
    if (src) {
      s.left = src->l();
      s.top = src->t();
      s.right = src->r();
      s.bottom = src->b();
    }

    t->DrawBitmap(b->d2d_bitmap()
        , !!dst ? &d : 0
        , 1.f, D2D1_BITMAP_INTERPOLATION_MODE::D2D1_BITMAP_INTERPOLATION_MODE_LINEAR
        , !!src ? &s : 0);
    
    return CVS_S_Ok;
  }
  Result D2DTarget::draw_bitmap(IBitmap *bitmap, const Point &pt, const Rect *src) {
    if (!bitmap) return CVS_E_InvalidParam;
    Size size;
    if (src) {
      size.w = src->w;
      size.h = src->h;
    } else {
      size = bitmap->size();
    }
    Rect rc(pt.x, pt.y, size.w, size.h);
    return draw_bitmap(bitmap, &rc, src);
  }

  Result D2DTarget::create_compatible_target(IBitmapTarget **target) {
    if (!target) return CVS_E_InvalidParam;
    auto t = new D2DBitmapTarget(d2d_render());
    if (!t) return CVS_E_OutOfMemory;
    auto rst = t->create(this);
    if (Failed(rst)) {
      SaveDecref(t);
      t = 0;
    }
    *target = t;
    return rst;
  }
  Result D2DTarget::create_bitmap(IBitmap **bitmap) {
    if (!bitmap) return CVS_E_InvalidParam;
    *bitmap = new D2DBitmap(d2d_render(), this);
    if (!*bitmap) return CVS_E_OutOfMemory;
    return CVS_S_Ok;
  }
  Result D2DTarget::create_bitmap_brush(IBitmapBrush **brush) {
    if (!brush) return CVS_E_InvalidParam;
    *brush = new D2DBitmapBrush(d2d_render(), this);
    if (!*brush) return CVS_E_OutOfMemory;
    return CVS_S_Ok;
  }
  Result D2DTarget::create_solid_color_brush(const Color &color, ISolidColorBrush **brush) {
    if (!brush) return CVS_E_InvalidParam;
    D2DSolidColorBrush *b = new D2DSolidColorBrush(d2d_render(), this);
    if (!b) return CVS_E_OutOfMemory;
    Result rst = b->create(color);
    if (Failed(rst)) {
      SaveDecref(b);
      b = 0;
    }
    *brush = b;
    return rst;
  }
  Result D2DTarget::create_linear_gradient_brush(const LinearGradientBrushProperties &props
          , IGradientStopCollection *stop_collection, ILinearGradientBrush **brush) {
    auto s = dynamic_cast<D2DGradientStopCollection*>(stop_collection);
    if (!brush || !s) return CVS_E_InvalidParam;
    auto b = new D2DLinearGradientBrush(d2d_render(), this);
    if (!b) return CVS_E_OutOfMemory;
    Result rst = b->create(props, s);
    if (Failed(rst)) {
      SaveDecref(b);
      b = 0;
    }
    *brush = b;
    return rst;
  }
  Result D2DTarget::create_radial_gradient_brush(const RadialGradientBrushProperties &props
          , IGradientStopCollection *stop_collection, IRadialGradientBrush **brush) {
    auto s = dynamic_cast<D2DGradientStopCollection*>(stop_collection);
    if (!brush || !s) return CVS_E_InvalidParam;
    auto b = new D2DRadialGradientBrush(d2d_render(), this);
    if (!b) return CVS_E_OutOfMemory;
    Result rst = b->create(props, s);
    if (Failed(rst)) {
      SaveDecref(b);
      b = 0;
    }
    *brush = b;
    return rst;
  }
  Result D2DTarget::create_gradient_stop_collection(const GradientStop *stops, uint count, ExtendMode mode
          , IGradientStopCollection **stop_ollection) {
    if (!stop_ollection || !stops || count == 0) return CVS_E_InvalidParam;
    auto s = new D2DGradientStopCollection(d2d_render(), this);
    if (!s) return CVS_E_OutOfMemory;
    Result rst = s->create(stops, count, mode);
    if (Failed(rst)) {
      SaveDecref(s);
      s = 0;
    }
    *stop_ollection = s;
    return rst;
  }

  // D2DSurfaceTarget impl
  D2DSurfaceTarget::D2DSurfaceTarget(D2DRender *render) : D2DTarget(render), surface_(0), d2d_target_(0) {}
  D2DSurfaceTarget::~D2DSurfaceTarget() { reset(); }
  Result D2DSurfaceTarget::surface(ISurface **surface) const {
    if (!surface) return CVS_E_InvalidParam;
    *surface = surface_;
    if (*surface) (*surface)->incref();
    return !!(*surface) ? CVS_S_Ok : CVS_E_Fail;
  }
  Result D2DSurfaceTarget::reset() {
    SaveDecref(surface_); surface_ = 0;
    if (d2d_target_) {
      d2d_target_->Release();
      d2d_target_ = 0;
    }
    return CVS_S_Ok;
  }
  Result D2DSurfaceTarget::create(const PixelFormat format, const Size &size) {
    reset();
    D2DRender *render = d2d_render();
    if (!render) return CVS_E_RenderNotRender;
    
    surface_ = new D2DSurface(render);
    if (!surface_) return CVS_E_OutOfMemory;
    
    Result rst = surface_->create(format, size);
    if (Succeeded(rst)) {
      ID2D1Factory *factory = d2d_factory();
      HRESULT hr = factory->CreateWicBitmapRenderTarget(
          surface_->wic_bitmap(),
          D2D1::RenderTargetProperties(),
          &d2d_target_);
      if (factory && SUCCEEDED(hr)) {
        return CVS_S_Ok;
      }
      rst = CVS_E_Fail;
    }

    reset();
    return rst;
  }
  ID2D1RenderTarget* D2DSurfaceTarget::d2d_target() const { return d2d_target_; }

  // D2DWindowTarget impl
  D2DWindowTarget::D2DWindowTarget(D2DRender *render) : D2DTarget(render), d2d_target_(0) {}
  D2DWindowTarget::~D2DWindowTarget() { reset(); }
  Result D2DWindowTarget::reset() {
    if (d2d_target_) {
      d2d_target_->Release();
      d2d_target_ = 0;
    }
    return CVS_S_Ok;
  }
  Result D2DWindowTarget::resize(Size size) {
    if (!d2d_target_) return CVS_E_RenderNoTarget;
    D2D1_SIZE_U sz;
    sz.width = size.w;
    sz.height = size.h;
    auto hr = d2d_target_->Resize(&sz);
    return SUCCEEDED(hr) ? CVS_S_Ok : CVS_E_Fail;
  }
  Result D2DWindowTarget::create(void *handle, const Size &size) {
    reset();
    auto r = d2d_factory();
    if (!r) return CVS_E_RenderNotRender;

    auto hr = r->CreateHwndRenderTarget(D2D1::RenderTargetProperties()
        , D2D1::HwndRenderTargetProperties((HWND)handle, D2D1::SizeU(size.w, size.h))
        , &d2d_target_);
    return SUCCEEDED(hr) ? CVS_S_Ok : CVS_E_Fail;
  }
  ID2D1RenderTarget* D2DWindowTarget::d2d_target() const { return d2d_target_; }

  // D2DBitmapTarget impl
  D2DBitmapTarget::D2DBitmapTarget(D2DRender *render) : D2DTarget(render), d2d_target_(0) {}
  D2DBitmapTarget::~D2DBitmapTarget() { reset(); }
  Result D2DBitmapTarget::reset() {
    if (d2d_target_) {
      d2d_target_->Release();
      d2d_target_ = 0;
    }
    return CVS_S_Ok;
  }
  Result D2DBitmapTarget::bitmap(IBitmap **_bitmap) {
    if (!_bitmap) return CVS_E_InvalidParam;
    if (!d2d_target_) return CVS_E_RenderNoTarget;
    
    auto bitmap = new D2DBitmap(d2d_render(), this);
    if (!bitmap) return CVS_E_OutOfMemory;

    ID2D1Bitmap *b = 0;
    if (SUCCEEDED(d2d_target_->GetBitmap(&b))) {
      if (Succeeded(bitmap->create(b))) {
        *_bitmap = bitmap;
        return CVS_S_Ok;
      }
      b->Release();
    }

    SaveDecref(bitmap);
    return CVS_E_Fail;
  }
  Result D2DBitmapTarget::create(D2DTarget *target) {
    if (!target) return CVS_E_InvalidParam;
    reset();
    auto t = target->d2d_target();
    if (!t) return CVS_E_RenderNoTarget;
    auto hr = t->CreateCompatibleRenderTarget(&d2d_target_);
    return SUCCEEDED(hr) ? CVS_S_Ok : CVS_E_Fail;
  }
  ID2D1RenderTarget* D2DBitmapTarget::d2d_target() const { return d2d_target_; }
  
  ///////////////////////////////////////////
  // D2DRender impl
  D2DRender::D2DRender(IFactory *factory)
      : factory_(factory)
      , d2d_dl_(0), dwrite_dl_(0)
      , d2d_factory_(0), dwrite_factory_(0)
      , text_render_(0) {
    CoInitializeEx(NULL, COINIT_MULTITHREADED);
  }
  D2DRender::~D2DRender() {
    reset();
    CoUninitialize();
  }
  RenderType D2DRender::type() const { return render_type(); }
  IFactory* D2DRender::factory() const { return factory_; }
  Result D2DRender::create_surface_target(const PixelFormat format, const Size &size, ISurfaceTarget **target) {
    if (!target) return CVS_E_InvalidParam;
    auto t = new D2DSurfaceTarget(this);
    if (!t) return CVS_E_OutOfMemory;
    auto rst = t->create(format, size);
    if (Failed(rst)) {
      SaveDecref(t);
      t = 0;
    }
    *target = t;
    return CVS_S_Ok;
  }
  Result D2DRender::create_window_target(void *handle, const Size &size, IWindowTarget **target) {
    if (!target) return CVS_E_InvalidParam;
    auto t = new D2DWindowTarget(this);
    if (!t) return CVS_E_OutOfMemory;
    auto rst = t->create(handle, size);
    if (Failed(rst)) {
      SaveDecref(t);
      t = 0;
    }
    *target = t;
    return CVS_S_Ok;
  }
  Result D2DRender::create_shape_line(const Point &pt1, const Point &pt2, IShapeLine **shape) {
    if (!shape) return CVS_E_InvalidParam;
    *shape = new D2DShapeLine(this);
    if (!*shape) return CVS_E_OutOfMemory;
    (*shape)->set(pt1, pt2);
    return CVS_S_Ok;
  }
  Result D2DRender::create_shape_polyline(const Point *pts, uint count, IShapePolyline **shape) {
    if (!shape) return CVS_E_InvalidParam;
    *shape = new D2DShapePolyline(this);
    if (!*shape) return CVS_E_OutOfMemory;
    (*shape)->set(pts, count);
    return CVS_S_Ok;
  }
  Result D2DRender::create_shape_bezier(const BezierSegment &bezier, IShapeBezier **shape) {
    if (!shape) return CVS_E_InvalidParam;
    *shape = new D2DShapeBezier(this);
    if (!*shape) return CVS_E_OutOfMemory;
    (*shape)->set(bezier);
    return CVS_S_Ok;
  }
  Result D2DRender::create_shape_arc(const ArcSegment &arc, IShapeArc **shape) {
    if (!shape) return CVS_E_InvalidParam;
    *shape = new D2DShapeArc(this);
    if (!*shape) return CVS_E_OutOfMemory;
    (*shape)->set(arc);
    return CVS_S_Ok;
  }
  Result D2DRender::create_shape_polygon(const Point *pts, uint count, IShapePolygon **shape) {
    if (!shape || !pts || count == 0) return CVS_E_InvalidParam;
    *shape = new D2DShapePolygon(this);
    if (!*shape) return CVS_E_OutOfMemory;
    (*shape)->set(pts, count);
    return CVS_S_Ok;
  }
  Result D2DRender::create_shape_rect(const Rect &rc, const Size &radius, IShapeRect **shape) {
    if (!shape) return CVS_E_InvalidParam;
    *shape = new D2DShapeRect(this);
    if (!*shape) return CVS_E_OutOfMemory;
    (*shape)->set(rc, radius);
    return CVS_S_Ok;
  }
  Result D2DRender::create_shape_circle(const Point &center, dtype radius, IShapeCircle **shape) {
    if (!shape) return CVS_E_InvalidParam;
    *shape = new D2DShapeCircle(this);
    if (!*shape) return CVS_E_OutOfMemory;
    (*shape)->set(center, radius);
    return CVS_S_Ok;
  }
  Result D2DRender::create_shape_ellipse(const Point &center, const Size &radius, IShapeEllipse **shape) {
    if (!shape) return CVS_E_InvalidParam;
    *shape = new D2DShapeEllipse(this);
    if (!*shape) return CVS_E_OutOfMemory;
    (*shape)->set(center, radius);
    return CVS_S_Ok;
  }
  Result D2DRender::create_shape_path(IShapePath **shape) {
    if (!shape) return CVS_E_InvalidParam;
    D2DShapePath *p = new D2DShapePath(this);
    if (!p) return CVS_E_OutOfMemory;
    Result rst = p->create();
    if (Failed(rst)) {
      SaveDecref(p);
      p = 0;
    }
    *shape = p;
    return rst;
  }
  Result D2DRender::create_shape_text(const wchar_t *text, uint len, const Rect &rect
          , const FontFormat &font, const TextFormat &format, IShapeText **shape) {
    if (!shape || !text || len == 0) return CVS_E_InvalidParam;
    *shape = new D2DShapeText(this);
    if (!*shape) return CVS_E_OutOfMemory;
    auto rst = (*shape)->set(text, len, rect, font, format);
    if (Failed(rst)) {
      SaveDecref(*shape);
      *shape = 0;
      return CVS_E_Fail;
    }
    return CVS_S_Ok;
  }
  Result D2DRender::create_stroke_style(const StrokeStyleProperties &props, const dtype *dashs, uint count
          , IStrokeStyle **stroke_style) {
    if (!stroke_style || !dashs || count == 0) return CVS_E_InvalidParam;
    auto s = new D2DStrokeStyle(this);
    if (!s) return CVS_E_OutOfMemory;
    Result rst = s->create(props, dashs, count);
    if (Failed(rst)) {
      SaveDecref(s);
      s = 0;
    }
    *stroke_style = s;
    return CVS_S_Ok;
  }
  Result D2DRender::create_surface(ISurface **surface) {
    if (!surface) return CVS_E_InvalidParam;
    *surface = new D2DSurface(this);
    if (!*surface) return CVS_E_OutOfMemory;
    return CVS_S_Ok;
  }

  Result D2DRender::reset() {
    if (text_render_) {
      text_render_->Release();
      text_render_ = 0;
    }
    if (dwrite_factory_) {
      dwrite_factory_->Release();
      dwrite_factory_ = 0;
    }
    if (d2d_factory_) {
      d2d_factory_->Release();
      d2d_factory_ = 0;
    }
    if (dwrite_dl_) {
      dwrite_dl_->release();
      dwrite_dl_ = 0;
    }
    if (d2d_dl_) {
      d2d_dl_->release();
      d2d_dl_ = 0;
    }
    return CVS_S_Ok;
  }
  Result D2DRender::init() {
    Result rst = create_dl(&d2d_dl_, "d2d1.dll");
    if (Succeeded(rst))
      rst = create_d2d_factory(d2d_dl_, &d2d_factory_);
    if (Succeeded(rst))
      rst = create_dl(&dwrite_dl_, "dwrite.dll");
    if (Succeeded(rst))
      rst = create_dwrite_factory(dwrite_dl_, &dwrite_factory_);
    if (Succeeded(rst))
      text_render_ = new CustomTextRender(d2d_factory_);
    if (Failed(rst) || !text_render_)
      reset();
    return rst;
  }
  ID2D1Factory* D2DRender::d2d_factory() const { return d2d_factory_; }
  IDWriteFactory* D2DRender::dwrite_factory() const { return dwrite_factory_; }
  CustomTextRender* D2DRender::text_render() const { return text_render_; }
  Result D2DRender::create_dl(IDynamicLibrary **dl, const char *name) {
    if (!dl) return CVS_E_InvalidParam;
    *dl = IDynamicLibrary::new_instance();
    if (!*dl) return CVS_E_OutOfMemory;
    if ((*dl)->open(name)) {
      return CVS_S_Ok;
    }
    (*dl)->release();
    (*dl) = 0;
    return CVS_E_RenderNotSupported;
  }
  Result D2DRender::create_d2d_factory(IDynamicLibrary *dl, ID2D1Factory **factory) {
    if (!dl || !factory) return CVS_E_InvalidParam;
    if (!dl->opened()) return CVS_E_RenderNotSupported;

    typedef HRESULT (WINAPI *funCreateFactory)(
        D2D1_FACTORY_TYPE          factoryType,
        REFIID                     riid,
        const D2D1_FACTORY_OPTIONS *pFactoryOptions,
        void                       **ppIFactory
      );
    funCreateFactory createFactory = (funCreateFactory)dl->proc("D2D1CreateFactory");
    if (!createFactory) return CVS_E_RenderNotSupported;

    createFactory(
        D2D1_FACTORY_TYPE::D2D1_FACTORY_TYPE_SINGLE_THREADED
      , __uuidof(ID2D1Factory)
      , NULL
      , (void**)(factory));
    
    return !!factory ? CVS_S_Ok : CVS_E_Fail;
  }
  Result D2DRender::create_dwrite_factory(IDynamicLibrary *dl, IDWriteFactory **factory) {
    if (!dl || !factory) return CVS_E_InvalidParam;
    if (!dl->opened()) return CVS_E_RenderNotSupported;

    typedef HRESULT (WINAPI *funCreateFactory)(
        DWRITE_FACTORY_TYPE factoryType,
        REFIID iid,
        IUnknown **factory
      );
    funCreateFactory createFactory = (funCreateFactory)dl->proc("DWriteCreateFactory");
    if (!createFactory) return CVS_E_RenderNotSupported;

    createFactory(
        DWRITE_FACTORY_TYPE::DWRITE_FACTORY_TYPE_SHARED
      , __uuidof(IDWriteFactory)
      , (IUnknown**)(factory));
    
    return !!factory ? CVS_S_Ok : CVS_E_Fail;
  }
  bool D2DRender::is_supported() {
    IDynamicLibrary *d2d_dl = 0;
    ID2D1Factory *d2d_factory = 0;
    IDynamicLibrary *dwrite_dl = 0;
    IDWriteFactory *dwrite_factory = 0;

    Result rst = create_dl(&d2d_dl, "d2d1.dll");
    if (Succeeded(rst))
      rst = create_d2d_factory(d2d_dl, &d2d_factory);
    if (Succeeded(rst))
      rst = create_dl(&dwrite_dl, "dwrite.dll");
    if (Succeeded(rst))
      rst = create_dwrite_factory(dwrite_dl, &dwrite_factory);

    if (d2d_factory) d2d_factory->Release();
    if (dwrite_factory) dwrite_factory->Release();
    if (d2d_dl) d2d_dl->release();
    if (dwrite_dl) dwrite_dl->release();
    return Succeeded(rst) ? true : false;
  }
} // end namespace c2d
} // end namespace cvs